package com.ebo.httpcall4j.core;

import com.alibaba.fastjson2.JSONObject;
import com.ebo.httpcall4j.core.log.HttpCallLogService;
import com.ebo.httpcall4j.core.model.HttpCallRequest;
import com.ebo.httpcall4j.core.model.HttpCallResponse;
import com.ebo.httpcall4j.core.model.enums.ContentType;
import com.ebo.httpcall4j.core.processor.HttpCallProcessor;
import com.ebo.httpcall4j.core.retry.RetryStrategy;
import okhttp3.*;
import org.springframework.context.ApplicationContext;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
 * http调用核心服务实现类
 */
public class HttpCallServiceImpl implements HttpCallService {

    private final OkHttpClient client;
    private final HttpCallLogService logService;
    private final ApplicationContext applicationContext;

    public HttpCallServiceImpl(OkHttpClient client, HttpCallLogService logService, ApplicationContext applicationContext) {
        this.client = client;
        this.logService = logService;
        this.applicationContext = applicationContext;
    }


    @Override
    public HttpCallResponse call(HttpCallRequest request, Class<? extends HttpCallProcessor> processorClass) throws IOException {

        HttpCallProcessor processor = applicationContext.getBean(processorClass);
        String[] beanNamesForType = applicationContext.getBeanNamesForType(processorClass);
        String beanName = beanNamesForType[0];

        processor.beforeRequest(request);
        Request.Builder requestBuilder;
        try {
            requestBuilder = createRequestBuilder(request, processor);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

        HttpCallResponse callResponse;
        try (Response response = client.newCall(requestBuilder.build()).execute()) {
            callResponse = toHttpCallResponse(response);
        }
        processor.afterResponse(callResponse);
        boolean isSuccess = processor.isSuccess(callResponse);
        callResponse.setSuccess(isSuccess);
        logService.log(beanName, request, callResponse, isSuccess, null);
        return callResponse;
    }


    @Override
    public CompletableFuture<HttpCallResponse> callAsync(HttpCallRequest request, Class<? extends HttpCallProcessor> processorClass) {

        HttpCallProcessor processor = applicationContext.getBean(processorClass);
        String[] beanNamesForType = applicationContext.getBeanNamesForType(processorClass);
        String beanName = beanNamesForType[0];

        processor.beforeRequest(request);
        Request.Builder requestBuilder;
        try {
            requestBuilder = createRequestBuilder(request, processor);
        } catch (UnsupportedEncodingException e) {
            CompletableFuture<HttpCallResponse> future = new CompletableFuture<>();
            future.completeExceptionally(e);
            return future;
        }

        CompletableFuture<HttpCallResponse> future = new CompletableFuture<>();
        client.newCall(requestBuilder.build()).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                logService.logException(beanName,request, e);
                future.completeExceptionally(e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                HttpCallResponse callResponse = toHttpCallResponse(response);
                boolean isSuccess = processor.isSuccess(callResponse);
                callResponse.setSuccess(isSuccess);
                logService.log(beanName, request, callResponse, isSuccess);
                future.complete(callResponse);
            }
        });
        return future;
    }

    /**
     * 创建OkHttp请求构建器
     *
     * @param request HttpCallRequest请求对象
     * @return OkHttp Request.Builder
     * @throws UnsupportedEncodingException 如果编码不支持
     */
    private Request.Builder createRequestBuilder(HttpCallRequest request, HttpCallProcessor processor) throws UnsupportedEncodingException {
        Request.Builder requestBuilder = new Request.Builder()
                .url(request.getUrl());

        if (processor != null) {
            requestBuilder.tag(HttpCallProcessor.class, processor);
        }

        if (request.isRetryable()) {
            if (request.getRetryStrategy() != null) {
                requestBuilder.tag(RetryStrategy.class, request.getRetryStrategy());
            } else {
                requestBuilder.tag(Boolean.class, true);
            }
        }

        if (request.getHeaders() != null) {
            request.getHeaders().forEach(requestBuilder::addHeader);
        }

        RequestBody body = null;
        if (request.getBody() != null) {
            ContentType contentType = request.getContentType();
            String requestBodyString;

            switch (contentType) {
                case APPLICATION_JSON:
                    requestBodyString = JSONObject.toJSONString(request.getBody());
                    break;
                case APPLICATION_X_WWW_FORM_URLENCODED:
                    Map<?, ?> formData = (Map<?, ?>) request.getBody();
                    StringBuilder formBodyBuilder = new StringBuilder();
                    for (Map.Entry<?, ?> entry : formData.entrySet()) {
                        if (formBodyBuilder.length() > 0) {
                            formBodyBuilder.append("&");
                        }
                        formBodyBuilder.append(URLEncoder.encode(String.valueOf(entry.getKey()), "UTF-8"))
                                .append("=")
                                .append(URLEncoder.encode(String.valueOf(entry.getValue()), "UTF-8"));
                    }
                    requestBodyString = formBodyBuilder.toString();
                    break;
                default:
                    requestBodyString = request.getBody().toString();
//                    throw new IllegalArgumentException("Unsupported content type: " + contentType);
            }
            body = RequestBody.create(requestBodyString, MediaType.parse(contentType.getValue()));
        }

        requestBuilder.method(request.getMethod().name(), body);
        return requestBuilder;
    }

    private HttpCallResponse toHttpCallResponse(Response response) throws IOException {
        HttpCallResponse callResponse = new HttpCallResponse();
        callResponse.setStatusCode(response.code());
        if (response.body() != null) {
            callResponse.setBody(response.body().string());
            callResponse.setOriginalBody(callResponse.getBody());
        }
        return callResponse;
    }
}